package com.it.miniodemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.it.miniodemo.mapper.SysUploadTaskMapper;
import com.it.miniodemo.minio.InitTaskParam;
import com.it.miniodemo.minio.MinioProperties;
import com.it.miniodemo.minio.MyMinioClient;
import com.it.miniodemo.minio.Result;
import com.it.miniodemo.model.SysUploadTask;
import com.it.miniodemo.model.dto.TaskRecordDTO;
import com.it.miniodemo.model.dto.UploadDto;
import com.it.miniodemo.model.vo.PartVo;
import com.it.miniodemo.model.vo.TaskInfoVo;
import com.it.miniodemo.service.MinioService;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.ListPartsResult;
import io.minio.messages.Part;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author
 * @date 2023/2/8 22:17
 **/
@Slf4j
@Service
public class MinioServiceImpl implements MinioService {
    @Autowired
    private SysUploadTaskMapper sysUploadTaskMapper;

    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MyMinioClient myMinioClient;

    @Autowired
    private MinioProperties minioProperties;

    /***
     * @description 初始化一个上传任务  -添加记录，获取uploadId
     * @param initTaskParam
     * @return com.it.miniodemo.minio.Result
     * @date 2023/2/28 12:33
     *
     */
    @Override
    public Result initTask(InitTaskParam initTaskParam) {
        String fileName = initTaskParam.getFileName();
        String bucketName = minioProperties.getBucket();
        final Long chunkSize = initTaskParam.getChunkSize();
        String extension = fileName.substring(fileName.lastIndexOf("."));
        final String contentType = getMimeTypeByExtension(extension);
        String minioDir = initTaskParam.getIdentifier() + extension;
        HashMultimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", contentType);
        CreateMultipartUploadResponse response = null;
        try {
            response = myMinioClient.uploadId(bucketName, null, minioDir, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("获取uploadId失败:{}", e);
        }
        String uploadId = response.result().uploadId();
        SysUploadTask task = new SysUploadTask();
        // 总大小除以分块的大小 得到分块的数量
        int chunkNum = (int) Math.ceil(initTaskParam.getTotalSize() * 1.0 / initTaskParam.getChunkSize());
        task.setBucketName(minioProperties.getBucket())
                .setChunkNum(chunkNum)
                .setChunkSize(initTaskParam.getChunkSize()) // 分块大小
                .setTotalSize(initTaskParam.getTotalSize()) // 文件大小
                .setFileIdentifier(initTaskParam.getIdentifier()) // 文件内容md5
                .setFileName(fileName) // 文件初始名字
                .setObjectKey(minioDir) // minio 上传的目录路径 url
                .setUploadId(uploadId);
        sysUploadTaskMapper.insert(task);
        return Result.ok(uploadId);
    }

    /***
     * @description 获取每个分片的上传url 根据分片的序号和uploadId，前端可以直接根据这个url上传对应的分片
     * @param uploadDto
     * @return com.it.miniodemo.minio.Result
     * @date 2023/2/28 12:33
     *
     */
    @Override
    public Result preSignUploadUrl(UploadDto uploadDto) {
        final SysUploadTask sysUploadTask = getSysUploadTask(uploadDto.getMd5());
        if (sysUploadTask == null) {
            return Result.error();
        }
        final String bucketName = sysUploadTask.getBucketName();
        final String fileName = sysUploadTask.getFileName();
        final String uploadId = sysUploadTask.getUploadId();
        final String objectKey = sysUploadTask.getObjectKey();
        //        // 3. 请求Minio 服务，获取每个分块带签名的上传URL
        Map<String, String> reqParams = new HashMap<>(3, 1);
        reqParams.put("partNumber", uploadDto.getPartNumber().toString()); // 对应的分块，如果没有填就默认是0，上传的文件在web端是可以看到的
        reqParams.put("uploadId", uploadId);

        final String url = myMinioClient.getPresignedObjectUrl(bucketName, objectKey, reqParams);


        return Result.ok(url);
    }

    /***
     * @description 获取上传的进度，先从数据库获取，没有的话就说明没有这个任务，前端就会调用创建一个上传任务
     * @param fileMd5
     * @return com.it.miniodemo.minio.Result
     * @date 2023/2/28 12:37
     *
     */
    @Override
    public Result taskInfo(String fileMd5) {
        final SysUploadTask sysUploadTask = getSysUploadTask(fileMd5);
        if (sysUploadTask == null) {
            return Result.ok(null);
        }
        // 说明有上传任务，那就先根据minio提供的api，去获取一下这个对象，
        // 没有的话说明上传了部分分片，
        //
        final String uploadId = sysUploadTask.getUploadId();
        final String objectKey = sysUploadTask.getObjectKey();
        final String bucketName = sysUploadTask.getBucketName();
        final GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(objectKey).build();
        GetObjectResponse object = null;
        try {
            object = myMinioClient.getObject(objectArgs);
        } catch (Exception e) { // 到这里说明上传了部分的分片
            /// 如果是上传了部分的分片，那就获取已经上传的分片，返回给前端，前端进行比较，再上传没上传的分片
            ListPartsResponse listPartsResponse = null;
            try {
                listPartsResponse = myMinioClient.listMultipart(minioProperties.getBucket(), null, objectKey,
                        1000, null, uploadId, null, null);
            } catch (Exception e1) {
                e1.printStackTrace();
                log.info("获取已上传的分片错误");
            }
            final ListPartsResult result = listPartsResponse.result();
            final List<Part> parts = result.partList();
            final List<PartVo> collect = parts.stream().map(part -> {
                PartVo partVo = new PartVo();
                BeanUtils.copyProperties(part, partVo);
                partVo.setSize(part.partSize());
                partVo.setLastModified(part.lastModified());
                return partVo;
            }).collect(Collectors.toList());
            // 返回已经上传的分片，前端重新计算上传进度
            final TaskInfoVo taskInfoVo = new TaskInfoVo().setFinished(false).setPath(sysUploadTask.getObjectKey())
                    .setTaskRecord(TaskRecordDTO.convertFromEntity(sysUploadTask).setExitPartList(collect));
            return Result.ok(taskInfoVo);
        }
        // 说明数据库存在，并且文件系统内容存在
        final TaskInfoVo taskInfoVo = new TaskInfoVo().setFinished(true).setPath(sysUploadTask.getObjectKey())
                .setTaskRecord(TaskRecordDTO.convertFromEntity(sysUploadTask));
        return Result.ok(taskInfoVo);
    }


    private SysUploadTask getSysUploadTask(String fileMd5) {
        return sysUploadTaskMapper.selectOne(new LambdaQueryWrapper<SysUploadTask>().eq(SysUploadTask::getFileIdentifier, fileMd5));
    }

    /***
     * @description 上传完成，就通知minio进行分片的合并
     * @param fileMd5
     * @return void
     * @date 2023/2/28 12:41
     *
     */
    @SneakyThrows
    @Override
    public void merge(String fileMd5) {
        final SysUploadTask sysUploadTask = getSysUploadTask(fileMd5);
        final String bucket = minioProperties.getBucket();
        final String objectKey = sysUploadTask.getObjectKey();
        final String uploadId = sysUploadTask.getUploadId();
//        final ListPartsResponse listPartsResponse = myMinioClient.listMultipart(bucket, null, objectKey, 1000, null, uploadId, null, null);
        try {
            //TODO::目前仅做了最大1000分片
            Part[] parts = new Part[1000];
            ListPartsResponse partResult = myMinioClient.listMultipart(minioProperties.getBucket(), null, objectKey, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            myMinioClient.completeMultipartUpload(minioProperties.getBucket(), null, objectKey, uploadId, parts, null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
//        myMinioClient.completeMultipartUpload(bucket,null,objectKey,uploadId,parts,null,null)
    }


    private String getMimeTypeByExtension(String extension) {
        String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if (StringUtils.isNotEmpty(extension)) {
            ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
            if (extensionMatch != null) {
                contentType = extensionMatch.getMimeType();
            }
        }
        return contentType;

    }
}
